#ifndef __RING_H__
#define __RING_H__

#include <string.h>

#define ROUND(val, limit)				\
    (((limit) & ((limit) - 1)) ?			\
    ((val) % (limit)) : ((val) & ((limit) - 1)))

typedef unsigned short ring_ptr_t;
typedef unsigned short ring_size_t;

typedef struct ring {
    volatile ring_ptr_t head;
    volatile ring_ptr_t tail;
    const ring_size_t size;
    unsigned char data[256];
} ring_t;

#define RING_GET_VARIABLES _IOR('q', 1, ring_t *)
#define RING_CLR_VARIABLES _IO('q', 2)
#define RING_SET_VARIABLES _IOW('q', 3, ring_t *)


#define __cat2(str1,str2)	str1##str2
#define cat2(str1,str2)		__cat2(str1,str2)


#define ring_length ring_data_size
#define ring_avail ring_free_size
#define ring_offset ring_get_data_ptr


static inline ring_ptr_t ring_round(ring_ptr_t val, ring_size_t limit)
{
    return val % limit;
}

/*
   The function returns the ring size.
*/
static inline ring_size_t ring_size(ring_t *r)
{
    return r->size;
}

/*
   The function checks if there's no free space available in the ring.
*/
static inline char ring_is_full(ring_t *r)
{
    return ring_round(r->head + 1, r->size) == r->tail;
}

/*
   The function checks if there's no data available in the ring.
*/
static inline char ring_is_empty(ring_t *r)
{
    return r->head == r->tail;
}

/*
   The function returns how many bytes of data are available in the ring.
*/
static inline ring_size_t ring_data_size(ring_t *r)
{
    return ring_round(r->size + r->head - r->tail, r->size);
}

/*
   The function returns how many bytes of free space available in the ring.
*/
static inline ring_size_t ring_free_size(ring_t *r)
{
    return ring_round(r->size + r->tail - r->head - 1, r->size);
}

/*
   The function returns a pointer to the ring's data at specified offset.
*/
static inline
ring_ptr_t ring_get_data_ptr(ring_t *r, ring_size_t off)
{
    return ring_round(r->tail + off, r->size);
}

/*
   The function sets the ring's data pointer to specified offset.
*/
static inline void ring_set_data_ptr(ring_t *r, ring_ptr_t h)
{
#ifdef DEBUG_RINGS
    if (h > r->size) {
	debug_puts_P(PSTR("setting data pointer "));
	debug_putdec(h);
	debug_puts_P(PSTR("%i of ring "));
	debug_puthex8((long)((char *)r - (char *)0) >> 24);
	debug_puthex8((long)((char *)r - (char *)0) >> 16);
	debug_puthex8((long)((char *)r - (char *)0) >> 8);
	debug_puthex8((long)((char *)r - (char *)0));
	debug_puts_P(PSTR("%p limits\n"));
    }
#endif
    r->tail = h;
}

/*
   The function returns a pointer to the ring's free space at specified offset.
*/
static inline ring_ptr_t ring_get_free_space_ptr(ring_t *r, ring_size_t off)
{
    return ring_round(r->head + off, r->size);
}

/*
   The function sets the ring's free space pointer to specified offset.
*/
static inline void ring_set_free_space_ptr(ring_t *r, ring_ptr_t h)
{
#ifdef DEBUG_RINGS
    if (h > r->size) {
	debug_puts_P(PSTR("setting fs pointer "));
	debug_putdec(h);
	debug_puts_P(PSTR("%i of ring "));
	debug_puthex8((long)((char *)r - (char *)0) >> 24);
	debug_puthex8((long)((char *)r - (char *)0) >> 16);
	debug_puthex8((long)((char *)r - (char *)0) >> 8);
	debug_puthex8((long)((char *)r - (char *)0));
	debug_puts_P(PSTR("%p limits\n"));
    }
#endif
    r->head = h;
}

/*
   The function puts a byte to the ring.
*/
static inline void ring_put(ring_t *r, unsigned char ch)
{
#ifdef DEBUG_RINGS
    if (ring_is_full(r)) {
	debug_puts_P(PSTR("adding data to full ring "));
	debug_puthex8((long)((char *)r - (char *)0) >> 24);
	debug_puthex8((long)((char *)r - (char *)0) >> 16);
	debug_puthex8((long)((char *)r - (char *)0) >> 8);
	debug_puthex8((long)((char *)r - (char *)0));
	debug_putc('\n');
    }
#endif
    r->data[r->head] = ch;
    r->head = ring_round(r->head + 1, r->size);
}

/*
   The function puts a byte of data to the ring at specified offset
   in free space without changing ring's data pointer. This way data
   is stored in the ring but is not accessible when reading from the ring.
*/
static inline
void ring_put_at_free_space(ring_t *r, unsigned char ch, ring_size_t off)
{
#ifdef DEBUG_RINGS
    if (ring_is_full(r)) {
	debug_puts_P(PSTR("storing data to full ring "));
	debug_puthex8((long)((char *)r - (char *)0) >> 24);
	debug_puthex8((long)((char *)r - (char *)0) >> 16);
	debug_puthex8((long)((char *)r - (char *)0) >> 8);
	debug_puthex8((long)((char *)r - (char *)0));
	debug_putc('\n');
    }
#endif

//printk("###zz debug###put 0x%x to ring\n",ch);

    r->data[ring_round(r->head + off, r->size)] = ch;
}

/*
   The function returns a byte from the ring.
*/
static inline unsigned char ring_get(ring_t *r)
{
#ifdef DEBUG_RINGS
    if (ring_is_empty(r)) {
	debug_puts_P(PSTR("getting data from empty ring "));
	debug_puthex8((long)((char *)r - (char *)0) >> 24);
	debug_puthex8((long)((char *)r - (char *)0) >> 16);
	debug_puthex8((long)((char *)r - (char *)0) >> 8);
	debug_puthex8((long)((char *)r - (char *)0));
	debug_putc('\n');
    }
#endif
    unsigned char data;
    data = r->data[r->tail];
    r->tail = ring_round(r->tail + 1, r->size);
    return data;
}

/*
   The function returns specified amount of data from the ring.
*/
static inline void ring_get_buf(ring_t *r, void *buf, ring_size_t size)
{
#ifdef DEBUG_RINGS
    if (ring_data_size(r) < size) {
	debug_puts_P(PSTR("getting more data than in ring "));
	debug_puthex8((long)((char *)r - (char *)0) >> 24);
	debug_puthex8((long)((char *)r - (char *)0) >> 16);
	debug_puthex8((long)((char *)r - (char *)0) >> 8);
	debug_puthex8((long)((char *)r - (char *)0));
	debug_putc('\n');
    }
#endif
    if (size > r->size - r->tail) {
	memcpy(buf, r->data + r->tail, r->size - r->tail);
	size -= r->size - r->tail;
	buf += r->size - r->tail;
	r->tail = 0;
    }
    memcpy(buf, r->data + r->tail, size);
    r->tail = ring_round(r->tail + size, r->size);
}

/*
   The function returns specified amount of data from the ring, but
   leaves the data pointer intact.
*/
static inline void ring_look_buf(ring_t *r, void *buf, ring_size_t size)
{
    ring_ptr_t tmp = r->tail;
#ifdef DEBUG_RINGS
    if (ring_data_size(r) < size) {
	debug_puts_P(PSTR("getting more data than in ring "));
	debug_puthex8((long)((char *)r - (char *)0) >> 24);
	debug_puthex8((long)((char *)r - (char *)0) >> 16);
	debug_puthex8((long)((char *)r - (char *)0) >> 8);
	debug_puthex8((long)((char *)r - (char *)0));
	debug_putc('\n');
    }
#endif
    if (size > r->size - tmp) {
	memcpy(buf, r->data + tmp, r->size - tmp);
	size -= r->size - tmp;
	buf += r->size - tmp;
	tmp = 0;
    }
    memcpy(buf, r->data + tmp, size);
}

/*
   The function puts specified amount of data to the ring.
*/
static inline void ring_put_buf(ring_t *r, const void *buf, ring_size_t size)
{
#ifdef DEBUG_RINGS
    if (ring_free_size(r) < size) {
	debug_puts_P(PSTR("putting more data than free space in ring "));
	debug_puthex8((long)((char *)r - (char *)0) >> 24);
	debug_puthex8((long)((char *)r - (char *)0) >> 16);
	debug_puthex8((long)((char *)r - (char *)0) >> 8);
	debug_puthex8((long)((char *)r - (char *)0));
	debug_putc('\n');
    }
#endif
    if (size > r->size - r->head) {
	memcpy(r->data + r->head, buf, r->size - r->head);
	size -= r->size - r->head;
	buf += r->size - r->head;
	r->head = 0;
    }
    memcpy(r->data + r->head, buf, size);
    r->head = ring_round(r->head + size, r->size);
}

/*
   The function puts a specified amount of data to the ring so
   that this data is returned first next time.
*/
static inline
void ring_put_buf_at_data_start(ring_t *r, const void *buf, ring_size_t size)
{
#ifdef DEBUG_RINGS
    if (ring_free_size(r) < size) {
	debug_puts_P(PSTR("storing more data than free space in ring "));
	debug_puthex8((long)((char *)r - (char *)0) >> 24);
	debug_puthex8((long)((char *)r - (char *)0) >> 16);
	debug_puthex8((long)((char *)r - (char *)0) >> 8);
	debug_puthex8((long)((char *)r - (char *)0));
	debug_putc('\n');
    }
#endif
    if (r->tail < size) {
	size -= r->tail;
	memcpy(r->data, buf + size, r->tail);
	r->tail = r->size;
    }
    r->tail -= size;
    memcpy(r->data + r->tail, buf, size);
}

/*
   The function returns a byte of data from the ring at specified offset
   without changing ring's data pointer.
*/
static inline unsigned char ring_look(ring_t *r, ring_size_t off)
{
#ifdef DEBUG_RINGS
    if (ring_data_size(r) <= off) {
	debug_puts_P(PSTR("looking at free space in ring "));
	debug_puthex8((long)((char *)r - (char *)0) >> 24);
	debug_puthex8((long)((char *)r - (char *)0) >> 16);
	debug_puthex8((long)((char *)r - (char *)0) >> 8);
	debug_puthex8((long)((char *)r - (char *)0));
	debug_putc('\n');
    }
#endif
    return r->data[ring_get_data_ptr(r, off)];
}

/*
   The function removes the specified amount of data from the ring
*/
static inline void ring_skip(ring_t *r, ring_size_t off)
{
#ifdef DEBUG_RINGS
    if (ring_data_size(r) < off) {
	debug_puts_P(PSTR("skipping more data than in ring "));
	debug_puthex8((long)((char *)r - (char *)0) >> 24);
	debug_puthex8((long)((char *)r - (char *)0) >> 16);
	debug_puthex8((long)((char *)r - (char *)0) >> 8);
	debug_puthex8((long)((char *)r - (char *)0));
	debug_putc('\n');
    }
#endif
    r->tail = ring_get_data_ptr(r, off);
}

/*
   The function clears all data from the ring
*/
static inline void ring_clear(ring_t *r)
{
    r->tail = r->head;
}

/*
   The function returns from the ring an unsigned short value stored as
   little endian data.
*/
static inline unsigned short ring_get_lew(ring_t *r)
{
#ifdef DEBUG_RINGS
    if (ring_data_size(r) < 2) {
	debug_puts_P(PSTR("get_lew in ring without data "));
	debug_puthex8((long)((char *)r - (char *)0) >> 24);
	debug_puthex8((long)((char *)r - (char *)0) >> 16);
	debug_puthex8((long)((char *)r - (char *)0) >> 8);
	debug_puthex8((long)((char *)r - (char *)0));
	debug_putc('\n');
    }
#endif

    unsigned short data;
    data = ring_get(r);
    data |= ring_get(r) << 8;
    return data;
}


static inline int ipmb_recv(ring_t * r, unsigned char *buf, int length)
{
    int cnt, len;
    /* get amount of data in the ring */
    cnt = ring_data_size(r);
    if (cnt)
    {
    	/* get message length */
    	len = ring_get(r);
    	if (len > length)
    	{
    		printf("ipmb_recv: truncate msg!\n");
    		len = length;
    	}
    	if (!len || len > cnt)
    	{
    		/* corrupted ring data, discard all */
    		ring_clear(r);
    		//printf("corrupted ring data, discard all. len = %d, cnt = %d\n",len,cnt);
    		len = 0;
    	}
    	else
    	{
    		ring_get_buf(r, buf, len);
    		//set ring tail to kernel ring
    		//ioctl(g_i2c_file, I2C_SET_RING, r);
    	}
    }
    else
    {
    	len = 0;
    }
    return len;
}
#endif /* __RING_H__ */
